#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/acpi.h>
#include <linux/uaccess.h>

#define CLASS_NAME "led5_7"
#define DEVICE_NAME "dev_led"
#define DEV0_NAME "led_gpio_5_0"
#define DEV1_NAME "led_gpio_1_1"
#define DEV2_NAME "led_gpio_0_13"

// Blue
#define REG0_5_0   0x00032B3015C
#define GPIO_5_DDR 0x00028039004
#define GPIO_5_DR  0x00028039000

// Green
#define REG0_1_1   0x00032B30060
#define GPIO_1_DDR 0x00028035004
#define GPIO_1_DR  0x00028035000

// Red
#define REG0_0_13  0x00032B30050
#define GPIO_0_DDR 0x00028034004
#define GPIO_0_DR  0x00028034000


MODULE_LICENSE("GPL");

// Device class
struct class *led_class;

// Device number
static dev_t dev0;
static dev_t dev1;
static dev_t dev2;

static int major = 0;

int led_open(struct inode *, struct file *);
ssize_t led_write(struct file *, const char __user *, size_t, loff_t *);
ssize_t led_read(struct file *, char __user *, size_t, loff_t *);
int led_close(struct inode *, struct file *);

// File operations
static struct file_operations led_ops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = led_close
};

int led_open(struct inode *inode, struct file *filep)
{
	printk(KERN_ERR "[%s] Open: %s\n", DEVICE_NAME, __func__);
	return 0;
}

ssize_t led_write(struct file *filep, const char __user *buf, size_t size, loff_t *len)
{
	int val;
	int ret;
	int led_index;
	int reg0;
	int gpio_ddr;
	int gpio_dr;
	int gpio_bit = 0;
	int pin_value_on;
	int pin_value_off;
	int mode_value;
	void __iomem *vaddr;

	printk(KERN_ERR "[%s] Write: %s Size: %ld\n", DEVICE_NAME, __func__, size);

	ret = copy_from_user(&val, buf, size);

	switch (val / 2) {
		case 0: // 0 or 1, GPIO 5_0
			led_index = 0;
			reg0 = REG0_5_0;
			gpio_ddr = GPIO_5_DDR;
			gpio_dr  = GPIO_5_DR;
			gpio_bit = 0;
			mode_value = 0x246;
			pin_value_on = 0x1;
			pin_value_off = 0xFFFFFFFE;
			break;
		case 1: // 2 or 3, GPIO 1_1
			led_index = 1;
			reg0 = REG0_1_1;
			gpio_ddr = GPIO_1_DDR;
			gpio_dr  = GPIO_1_DR;
			gpio_bit = 1;
			mode_value = 0x245;
			pin_value_on = 0x2;
			pin_value_off = 0xFFFFFFFD;
			break;
		case 2: // 4 or 5, GPIO 0_13
			led_index = 2;
			reg0 = REG0_0_13;
			gpio_ddr = GPIO_0_DDR;
			gpio_dr  = GPIO_0_DR;
			gpio_bit = 13;
			mode_value = 0x045;
			pin_value_on = 0x2;
			pin_value_off = 0xFFFFFFFD;
			break;
		default:
			printk(KERN_ERR "Invalid value: %d\n", val);
			return 0;
	}

	// Set GPIO Pin Mode / Function / Level
	vaddr = ioremap(reg0, 4);
	writel(mode_value, vaddr);

	// Set Output Mode
	vaddr = ioremap(gpio_ddr, 4);
	writel(readl(vaddr) | (1 << gpio_bit), vaddr);

	// Write Data
	vaddr = ioremap(gpio_dr, 4);
	if (val % 2 == 0) {
		writel(readl(vaddr) | pin_value_on, vaddr);
		printk(KERN_ERR "LED %d On!\n", led_index);
	} else {
		writel(readl(vaddr) & pin_value_off, vaddr);
		printk(KERN_ERR "LED %d Off!\n", led_index);
	}

	return 0;
}

ssize_t led_read(struct file *filep, char __user *buf, size_t size, loff_t *len)
{
	printk(KERN_ERR "[%s] Read: %s Size: %ld\n", DEVICE_NAME, __func__, size);
	return 0;
}

int led_close(struct inode *inode, struct file *filep)
{
	printk(KERN_ERR "[%s] Close: %s\n", DEVICE_NAME, __func__);
	return 0;
}

int __init chr_dev_init(void)
{
	printk(KERN_ERR "Char Device Init\n");

	// Register char device
	major = register_chrdev(0, DEVICE_NAME, &led_ops);
	printk(KERN_ERR "Registered device %s: Major: %d\n", DEVICE_NAME, major);

	// Create class
	led_class = class_create(THIS_MODULE, CLASS_NAME);

	// Create devices
	dev0 = MKDEV(major, 0);
	device_create(led_class, NULL, dev0, NULL, DEV0_NAME);
	dev1 = MKDEV(major, 1);
	device_create(led_class, NULL, dev1, NULL, DEV1_NAME);
	dev2 = MKDEV(major, 2);
	device_create(led_class, NULL, dev2, NULL, DEV2_NAME);

	return 0;
}

void __exit chr_dev_exit(void)
{
	printk(KERN_ERR "Char Device Exit\n");

	// Destory devices
	device_destroy(led_class, dev0);
	device_destroy(led_class, dev1);
	device_destroy(led_class, dev2);

	// Destroy class
	class_destroy(led_class);

	// Unregister char device
	unregister_chrdev(major, DEVICE_NAME);
}

module_init(chr_dev_init);

module_exit(chr_dev_exit);

